// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2016-2019, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include "lombo_dc_priv.h"

static int lombo_dc_blk_atomic_check(struct drm_plane *plane,
	struct drm_plane_state *state)
{
	DRM_DEBUG_ATOMIC("%s:%d\n", __func__, __LINE__);
	return 0;
}

static void lombo_dc_blk_atomic_disable(struct drm_plane *plane,
	struct drm_plane_state *old_state)
{
	struct lombo_dc_blk_bl *bl = to_lombo_dc_blk_bl(plane);
	struct lombo_dc_blk *blk = bl->blk;
	struct lombo_dc *dc = blk->dc;

	bl->enable = 0;
	bl->dirty = 1;

	DRM_DEBUG_ATOMIC("dc%d.bl%d disable\n", dc->id, bl->id);

	blk->enable_mask &= ~(1u << bl->id);
	if (!blk->enable_mask)
		csp_dc_blocklinker_disable(dc->id);

	/* keep vblank enable for disable cursor */
	if (old_state->crtc) {
		drm_crtc_vblank_get(old_state->crtc);
		drm_crtc_vblank_put(old_state->crtc);
	}
}

static void lombo_dc_blk_atomic_update(struct drm_plane *plane,
	struct drm_plane_state *old_state)
{
	struct lombo_dc_blk_bl *bl = to_lombo_dc_blk_bl(plane);
	struct lombo_dc_blk *blk = bl->blk;
	struct lombo_dc *dc = blk->dc;
	dpu_fb_t *dpu_fb = &bl->dpu_fb;
	dpu_rectangle_t *rect = &bl->rect;
	dpu_alpha_mode_t alpha_mode;
	uint32_t enable_mask;

	struct drm_plane_state *state = plane->state;
	struct drm_framebuffer *fb = state->fb;
	struct drm_gem_cma_object *cma_obj =
			drm_fb_cma_get_gem_obj(fb, 0);
	const struct drm_format_info *fmt_info = fb->format;

	DRM_DEBUG_ATOMIC("dc%d.bl%d\n", dc->id, bl->id);
	DRM_DEBUG_ATOMIC("bl%d enable_mask=%u\n", bl->id, blk->enable_mask);
	DRM_DEBUG_ATOMIC("crtc[%d,%d,%u,%u], src[%u,%u,%u,%u]\n",
		state->crtc_x, state->crtc_y, state->crtc_w, state->crtc_h,
		state->src_x >> 16, state->src_y >> 16,
		state->src_w >> 16, state->src_h >> 16);

	dpu_fb->addr[0] = cma_obj->paddr + fb->offsets[0]
		+ fb->pitches[0] * (state->src_y >> 16)
		+ fmt_info->cpp[0] * (state->src_x >> 16);
	dpu_fb->addr[0] <<= 3;
	dpu_fb->format = lombo_dc_convert_drm_format(fmt_info->format);
	dpu_fb->pixel_order = 0;
	dpu_fb->width[0] = state->src_w >> 16;
	dpu_fb->height[0] = state->src_h >> 16;
	dpu_fb->planes = 1;
	dpu_fb->linestride_bit[0] = fb->pitches[0] << 3;
	rect->x = state->crtc_x;
	rect->y = state->crtc_y;
	rect->w = state->crtc_w;
	rect->h = state->crtc_h;

	bl->enable |= LOMBO_BLK_COMMIT_FLAG_ENABLE;
	bl->dirty = 1;

	enable_mask = blk->enable_mask;
	blk->enable_mask |= (1u << bl->id);

	if (enable_mask && (blk->enable_mask != 1))
		return;

	csp_dc_blocklinker_set_format(dc->id, dpu_fb->format);

	if (!fmt_info->has_alpha)
		alpha_mode = DPU_PLANE_ALPHA; /* no-px-al */
	else if (state->alpha != DRM_BLEND_ALPHA_OPAQUE)
		alpha_mode = DPU_PLANE_PIXEL_ALPHA; /* px-al & gb-al */
	else
		alpha_mode = DPU_PIXEL_ALPHA; /* px-al & no-gb-al */
	csp_dc_blocklinker_set_alpha_mode(dc->id, alpha_mode);
	csp_dc_blocklinker_set_alpha_value(dc->id, state->alpha >> 8);
	csp_dc_blocklinker_set_blend_rule(dc->id, 0, DPU_BLEND_SRC_OVER);

	csp_dc_blocklinker_enable(dc->id);

	/* keep vblank enable for update cursor */
	if (state->crtc) {
		drm_crtc_vblank_get(state->crtc);
		drm_crtc_vblank_put(state->crtc);
	}
}

static int lombo_dc_blk_bl_update_plane(struct drm_plane *plane,
		struct drm_framebuffer *fb,
		int crtc_x, int crtc_y,
		unsigned int crtc_w, unsigned int crtc_h,
		uint32_t src_x, uint32_t src_y,
		uint32_t src_w, uint32_t src_h)
{
	struct lombo_dc_blk_bl *bl = to_lombo_dc_blk_bl(plane);
	struct lombo_dc_blk *blk = bl->blk;
	struct lombo_dc *dc = blk->dc;
	dpu_fb_t *dpu_fb = &bl->dpu_fb;
	dpu_rectangle_t *rect = &bl->rect;
	dpu_alpha_mode_t alpha_mode;
	uint32_t enable_mask;

	struct drm_gem_cma_object *cma_obj =
			drm_fb_cma_get_gem_obj(fb, 0);
	const struct drm_format_info *fmt_info = fb->format;

	DRM_DEBUG_KMS("bl%d enable_mask=%u\n", bl->id, blk->enable_mask);
	DRM_DEBUG_KMS("crtc[%d,%d,%u,%u], src[%u,%u,%u,%u]\n",
		      crtc_x, crtc_y, crtc_w, crtc_h,
		      src_x >> 16, src_y >> 16, src_w >> 16, src_h >> 16);

	dpu_fb->addr[0] = cma_obj->paddr + fb->offsets[0]
		+ fb->pitches[0] * (src_y >> 16)
		+ fmt_info->cpp[0] * (src_x >> 16);
	dpu_fb->addr[0] <<= 3;
	dpu_fb->format = lombo_dc_convert_drm_format(fmt_info->format);
	dpu_fb->pixel_order = 0;
	dpu_fb->width[0] = src_w >> 16;
	dpu_fb->height[0] = src_h >> 16;
	dpu_fb->planes = 1;
	dpu_fb->linestride_bit[0] = fb->pitches[0] << 3;
	rect->x = crtc_x;
	rect->y = crtc_y;
	rect->w = crtc_w;
	rect->h = crtc_h;

	bl->enable = 1;
	bl->dirty = 1;

	enable_mask = blk->enable_mask;
	blk->enable_mask |= (1u << bl->id);

	if (enable_mask && (blk->enable_mask != 1))
		return 0;

	csp_dc_blocklinker_set_format(dc->id, dpu_fb->format);

	if (!fmt_info->has_alpha)
		alpha_mode = DPU_PLANE_ALPHA; /* no-px-al */
	else
		alpha_mode = DPU_PIXEL_ALPHA; /* px-al & no-gb-al */
	csp_dc_blocklinker_set_alpha_mode(dc->id, alpha_mode);
	csp_dc_blocklinker_set_alpha_value(dc->id, 0xFF);
	csp_dc_blocklinker_set_blend_rule(dc->id, 0, DPU_BLEND_SRC_OVER);

	csp_dc_blocklinker_enable(dc->id);

	return 0;
}

static int lombo_dc_blk_bl_disable_plane(struct drm_plane *plane)
{
	struct lombo_dc_blk_bl *bl = to_lombo_dc_blk_bl(plane);

	DRM_DEBUG_KMS("bl%d disable\n", bl->id);

	memset(&bl->dpu_fb, 0, sizeof(bl->dpu_fb));
	memset(&bl->rect, 0, sizeof(bl->rect));
	bl->enable = 0;
	bl->dirty = 1;

	bl->blk->enable_mask &= ~(1u << bl->id);
	if (!bl->blk->enable_mask)
		csp_dc_blocklinker_disable(bl->blk->dc->id);

	return 0;
}


static const struct drm_plane_helper_funcs dc_blk_helper_funcs = {
	.atomic_check	= lombo_dc_blk_atomic_check,
	.atomic_disable	= lombo_dc_blk_atomic_disable,
	.atomic_update	= lombo_dc_blk_atomic_update,
};

static bool lombo_dc_blk_format_mod_supported(struct drm_plane *plane,
	uint32_t format, uint64_t modifier)
{
	if (modifier) {
		DRM_INFO("dc blk not support modifier\n");
		return false;
	}

	return true;
}

static const struct drm_plane_funcs dc_blk_funcs = {
	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
	.destroy		= drm_plane_cleanup,
	.disable_plane		= drm_atomic_helper_disable_plane,
	.reset			= drm_atomic_helper_plane_reset,
	.update_plane		= drm_atomic_helper_update_plane,
	.format_mod_supported	= lombo_dc_blk_format_mod_supported,
};

static const uint32_t dc_blk_formats[] = {
	DRM_FORMAT_ARGB8888,
	DRM_FORMAT_ABGR8888,
	DRM_FORMAT_RGBA8888,
	DRM_FORMAT_BGRA8888,
	DRM_FORMAT_XRGB8888,
	DRM_FORMAT_XBGR8888,
	DRM_FORMAT_RGBX8888,
	DRM_FORMAT_BGRX8888,
};

struct lombo_dc_blk *lombo_dc_blk_init(struct lombo_dc *dc,
	enum drm_plane_type type, unsigned int zpos)
{
	struct drm_mode_config *config = &dc->drm->mode_config;
	struct lombo_dc_blk *blk;
	int i;
	int ret;
	uint32_t plane_id[LOMBO_DC_BLK_BL_NUM] = { 0 };
	uint32_t possible_crtcs;

	blk = devm_kzalloc(dc->dev, sizeof(*blk), GFP_KERNEL);
	if (!blk)
		return ERR_PTR(-ENOMEM);

	possible_crtcs = drm_crtc_mask(&dc->ecrtc->crtc);
	for (i = 0; i < ARRAY_SIZE(blk->bl_array); i++) {
		struct lombo_dc_blk_bl *bl = &blk->bl_array[i];

		ret = drm_universal_plane_init(dc->drm, &bl->base,
					       possible_crtcs,
					       &dc_blk_funcs,
					       dc_blk_formats,
					       ARRAY_SIZE(dc_blk_formats),
					       NULL, type, NULL);
		if (ret) {
			DRM_DEV_ERROR(dc->dev, "bl init fail\n");
			return ERR_PTR(ret);
		}
		/* make blocklinker private except first */
		if (i && !dc->user_blk) {
			list_del_init(&bl->base.head);
			config->num_total_plane--;
		}
		drm_plane_helper_add(&bl->base, &dc_blk_helper_funcs);

		bl->blk = blk;
		bl->id = i;
		plane_id[i] = bl->base.base.id;

		if (i)
			continue;

		ret = drm_plane_create_zpos_immutable_property(&bl->base,
							       zpos);
		if (ret) {
			DRM_DEV_ERROR(dc->dev,
				"add zpos%d property fail\n", zpos);
			return ERR_PTR(ret);
		}

		/* alpha value of global */
		ret = drm_plane_create_alpha_property(&bl->base);
		if (ret) {
			DRM_DEV_ERROR(dc->dev,
				"Couldn't add alpha property\n");
			return ERR_PTR(ret);
		}
	}

	blk->dc = dc;
	if (dc->user_blk)
		dc->ecrtc->bl_plane_id = drm_property_create_blob(dc->drm,
								  i * 4,
								  plane_id);

	return blk;
}

int lombo_dc_blk_update_fbdev_for_win(struct lombo_dc *dc,
	struct lombo_dc_win *win, struct drm_framebuffer *fb)
{
	struct lombo_dc_blk *blk = dc->blk;
	struct lombo_dc_blk_bl *bl;
	int i, opaque_num;
	int crtc_x, crtc_y;
	uint32_t crtc_w, crtc_h;
	uint32_t src_x, src_y, src_w, src_h;

	DRM_DEBUG_KMS("win opaque_num=%d, blk opaque_num=%d\n",
		      win->opaque_num, blk->opaque_fb_num);

	if (!fb || win->opaque_num < 1)
		opaque_num = 0;
	else
		opaque_num = win->opaque_num;

	for (i = 0; i < opaque_num; i++) {
		bl = &blk->bl_array[i];
		crtc_x = win->opaque[i].x;
		crtc_y = win->opaque[i].y;
		crtc_w = win->opaque[i].w;
		crtc_h = win->opaque[i].h;
		src_x = win->base.state->src_x + (crtc_x << 16);
		src_y = win->base.state->src_y + (crtc_y << 16);
		src_w = (crtc_w << 16);
		src_h = (crtc_h << 16);
		lombo_dc_blk_bl_update_plane(&bl->base, fb,
					     crtc_x, crtc_y, crtc_w, crtc_h,
					     src_x, src_y, src_w, src_h);
	}
	for (; i < blk->opaque_fb_num; i++) {
		bl = &blk->bl_array[i];
		lombo_dc_blk_bl_disable_plane(&bl->base);
	}
	blk->opaque_fb_num = opaque_num;

	return 0;
}

void lombo_dc_blk_commit(struct lombo_dc_blk *blk)
{
	struct lombo_dc *dc = blk->dc;

	uint8_t dirty = 0;
	uint32_t i;

	if (!blk)
		return;
	dc = blk->dc;

	for (i = 0; i < ARRAY_SIZE(blk->bl_array); i++) {
		if (blk->bl_array[i].dirty) {
			dirty = 1;
			break;
		}
	}
	if (!dirty)
		return;

	for (i = 0; i < ARRAY_SIZE(blk->bl_array); i++) {
		struct lombo_dc_blk_bl *bl = &blk->bl_array[i];
		struct lombo_dc_blk_bl *prev = i ? (bl - 1) : NULL;

		if (!bl->dirty)
			continue;
		bl->dirty = 0;

		if (!bl->enable) {
			if (prev)
				csp_dc_blocklinker_set_next(dc->id,
							    prev->id, 0);
			csp_dc_blocklinker_set_next(dc->id, bl->id, 0);
			continue;
		}

		csp_dc_blocklinker_set_buffer(dc->id, bl->id, &bl->dpu_fb);
		csp_dc_blocklinker_set_dest_rectangle(dc->id,
						      bl->id,
						      &bl->rect);
		if (prev)
			csp_dc_blocklinker_set_next(dc->id, prev->id, bl->id);
	}
}
